﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace MyGames
{
    // IMPROVE NOTE:
    // 'Decision making' area can be improved
    enum AttackEffect { Normal, Stun, Burn };

    class Enemy : Character
    {
        static ContentManager content = GameLoader.Content;
        static Player player;
        static EnemyProjectileManager projects;
        public static void Initalize(Player player, EnemyProjectileManager projects)
        {
            Enemy.player = player;
            Enemy.projects = projects;
        }


        #region Movement Declaration
        public enum EnemyState { Wandering, Confusing, Chasing, Attacking, Fleeing, Dead };

        // Movement and AI zone       
        static int ActiveRange = 1000;
        static int WanderMaxMoves = 3;
        static int WanderDistance = 200;
        static float DistanceEpsilon = 10.0f;
        static float WanderDelaySeconds = 3.0f;
        static float AttackDelaySeconds = 0.1f;
        static float FleeDelaySeconds = 3f;
        static float DeadDelaySeconds = 2f;
        static float GethitDelaySeconds = 0.3f;
        static float InvicibleTime = 3f;

        // State movement
        protected int wanderMovesCount;
        protected Vector2 wandersStartPosition;
        protected Vector2 wandersDestination;
        protected EnemyState state;
        protected bool fleedBefore;
        protected int getHitCount;
        protected EnemyProjectile project;
        protected bool attacking = false;
        protected float nextAttackTime;
        #endregion

        #region Declaration
        protected ModelType type;
        protected AttackType attackType;
        protected Element element;
        protected int attackRange = 100;
        protected int sighRange = 1000;
        protected int expValue;
        protected AttackEffect attackEffect = AttackEffect.Normal;
        #endregion

        #region Properties
        public ModelType EnemyType
        {
            get { return type; }
        }

        public int AttackRange
        {
            get { return attackRange; }
        }

        public int SighRange
        {
            get { return sighRange; }
        }

        public EnemyState State
        {
            get { return state; }
        }

        public int ExperienceWorth
        {
            get { return expValue; }
        }

        public AttackEffect AttackEffect
        {
            get { return attackEffect; }
            set
            {
                attackEffect = value;
                if (attackEffect == MyGames.AttackEffect.Normal)
                    affectColor = Color.White;
            }
        }
        #endregion


        #region Helper methods
        protected void ChangeState(EnemyState state)
        {
            if (state == EnemyState.Wandering)
            {
                //wandersStartPosition = worldLocation;
                wandersDestination = worldLocation;
            }

            velocity = Vector2.Zero;
            PlayAnimation("idle");
            this.state = state;
        }

        protected Vector2 RandomPosition(Vector2 start, int radius)
        {
            Vector2 s = new Vector2(RandomHelper.rand.Next(1, radius) * (RandomHelper.rand.Next(2) - 1),
                                    RandomHelper.rand.Next(1, radius / 3) * (RandomHelper.rand.Next(2) - 1));

            if (start.X < 100) s.X = Math.Abs(s.X);
            if (start.X > Camera.WorldRectangle.Width - 300) s.X = -Math.Abs(s.X);
            if (start.Y < Const.ViewportTopMost - 10) s.Y = Math.Abs(s.Y);
            if (start.Y > Const.ViewportBottomMost - 10) s.Y = -Math.Abs(s.Y);

            Vector2 destination = start + s;
            destination.X = MathHelper.Clamp(destination.X, 0, Camera.WorldRectangle.Width - animations[currentAnimation].FrameWidth);
            destination.Y = MathHelper.Clamp(destination.Y, Const.ViewportTopMost, Const.ViewportBottomMost);

            return destination;
        }

        protected void Move(Vector2 direction)
        {
            if (direction.X < 0) flipped = true;
            if (direction.X > 0) flipped = false;
            PlayAnimation("Move", false, true, false);
            velocity = direction * Speed;
            velocity.Y *= 0.4f;
        }
        #endregion

        #region Decision making
        protected bool SeePlayer()
        {
            // Cheack if the enemy saw the player, use distance and face direction
            float distance = Math.Abs(player.WorldLocation.X - worldLocation.X);
            bool faced = (player.WorldLocation.X > worldLocation.X && !flipped) ||
                         (player.WorldLocation.X < worldLocation.X && flipped);

            // If the player Move too near, enemy won't see, they "notice"! Else,
            // they must face the hero too spot him
            return (distance < 0.5 * sighRange) || (distance <= sighRange && faced || distance <= 100);
        }

        protected bool ShouldChasePlayer()
        {
            // When enemy see something suspicous, they will choose to chase after it,
            // or leave it and continue their Wandering. The nearer to the enemy, the
            // more chance player will be chased by the enemy.

            // IMPROVE NOTE: implement difficult and AI manager and then pass a param
            // to this fucntion or a static field in this class to improve the enemy's AI

            // For now: the chance that enemy will follow the player at max sigh range is
            // 'leaveChance' and at (0.6 * sigh) range is 100%, lower leave chance to make
            // enemy more hostile

            float leaveChance = 0.8f;
            float prange = (worldLocation - player.WorldLocation).Length() / SighRange;
            return RandomHelper.rand.NextDouble() <= (1 - leaveChance / 0.4 * (prange - 0.6));
        }

        protected bool CanAttack()
        {
            // Check if the player is in Attack range
            //if (attackRange <= 100) return IsCollideWith(player, true);

            return (player.WorldLocation - worldLocation).Length() <= attackRange &&
                Math.Abs(worldLocation.Y - player.WorldLocation.Y) <= 10;
        }

        protected bool LosedRange()
        {
            float distance = (player.WorldLocation - worldLocation).Length();
            return distance >= 1.2f * SighRange;
        }

        protected bool ShouldFlee()
        {
            // IMPROVE NOTE:
            // Difficulty and AI may pass to this fucntion to improve the decision
            // E.g: the enemy won't Flee if it's near an enemy of type healer or a boss

            // FOR NOW:
            // If health < 30%, the enemy will think about fleeing away from the player
            // The lower their health is, the clearer that they should Flee
            // Current rate: 60% they will Flee at 5% health, and 10% they will Flee at 30% health

            if (!IsCollideWith(player, false)) return false;

            float phealth = health * 1f / maxHealth;
            float startchance = 0.5f;

            return phealth < 0.3 && RandomHelper.rand.NextDouble() <= (0.9 - startchance / 0.25 * (phealth - 0.05));
        }

        protected bool Safe()
        {
            return (player.WorldLocation - worldLocation).Length() > 3 * sighRange;
        }
        #endregion

        #region Action
        protected void Wandering(GameTime time)
        {
            // Calculated Move length (distance from current position to destination)
            Vector2 direction = wandersDestination - worldLocation;
            float distance = direction.Length();

            // Reached the destination position
            if (distance < DistanceEpsilon)
            {
                PlayAnimation("idle");
                velocity = Vector2.Zero;

                // Generate new random position
                if (wanderMovesCount < WanderMaxMoves)
                {
                    wandersDestination = RandomPosition(worldLocation, WanderDistance);
                    wanderMovesCount++;
                }
                // Go back to the start position
                else
                {
                    wandersDestination = wandersStartPosition;
                    wanderMovesCount = 0;
                }

                // Next time wander
                nextActionTime = (float)time.TotalGameTime.TotalSeconds + WanderDelaySeconds * (1 + (float)RandomHelper.rand.NextDouble());
            }

            // Wait for the next action time
            if (time.TotalGameTime.TotalSeconds > nextActionTime)
            {
                direction.Normalize();
                Move(direction);
            }
        }

        protected void ChasePlayer(GameTime time)
        {
            if (nextActionTime > (float)time.TotalGameTime.TotalSeconds) return;

            // Move toward the player
            Vector2 dir = (player.WorldLocation - worldLocation);
            if (dir.Length() <= attackRange) dir.X = 0;
            dir.Normalize();
            Move(dir);
        }

        protected void Flee(GameTime time)
        {
            Vector2 dir = (player.WorldLocation - worldLocation);
            dir.Y = dir.X * 0.3f;
            dir.Normalize();
            Move(-1.5f * dir);

            nextActionTime = (float)time.TotalGameTime.TotalSeconds + FleeDelaySeconds;
        }

        protected void Attack(GameTime time)
        {
            // IMPROVE NOTE:
            // Affect by AI, enemy's type. The inherit class may override this method.
            // E.g: mages use magic more regular than other's class

            // FOR NOW:
            // Random!
            if (time.TotalGameTime.TotalSeconds < nextAttackTime)
                return;

            nextAttackTime = (float)time.TotalGameTime.TotalSeconds + AttackDelaySeconds + attackSpeed;

            if (player.WorldLocation.X < worldLocation.X) flipped = true;
            if (player.WorldLocation.X > worldLocation.X) flipped = false;
            int choice = 0;
            if (maxMana != 0)
            {
                float chance = (float)RandomHelper.rand.NextDouble();
                if (mana > 0.1f * maxMana && chance <= 0.33f) choice = 1;
                if (mana > 0.2f * maxMana && chance <= 0.15f) choice = 2;
            }

            Vector2 dir = player.WorldLocation - worldLocation;
            dir.Normalize(); dir.Y = 0;

            project = new EnemyProjectile(attackType, element, choice);
            project.Range = attackRange + 100;
            project.StartPosition = WorldCenter;
            project.Velocity = attackSpeed * 80 * dir;

            switch (choice)
            {
                case 0:
                    project.Value = attackPower;
                    PlayAnimation("Attack");
                    break;

                case 1:
                    project.Value = attackPower * 2;
                    mana -= 0.1f * maxMana;
                    PlayAnimation("skill1");
                    break;

                case 2:
                    project.Value = attackPower * 2;
                    mana -= 0.2f * maxMana;
                    PlayAnimation("skill2");
                    break;
            }

            //projects.AddEnemyProject(project);
            attacking = true;
        }
        #endregion


        #region Update and Draw
        public override void Update(GameTime gameTime)
        {
            // Check active or inactive
            float time = (float)gameTime.TotalGameTime.TotalSeconds;
            float distance = worldLocation.X - Camera.Position.X;
            enabled = (-ActiveRange <= distance && distance < ActiveRange + Camera.ViewPortWidth);
            if (!enabled) { base.Update(gameTime); return; }

            #region Effect
            switch (attackEffect)
            {
                case MyGames.AttackEffect.Stun:
                    velocity = Vector2.Zero;
                    invicible = false;
                    affectColor = Color.Yellow;
                    return;

                case MyGames.AttackEffect.Burn:
                    Health -= (float)gameTime.ElapsedGameTime.TotalSeconds * MaxHealth / 100;
                    affectColor = Color.Red;
                    break;

                default:
                    break;
            }
            #endregion

            #region Dying
            // Check live or dead
            if (Health <= 0)
            {
                velocity = Vector2.Zero;
                if (currentAnimation != "die")
                {
                    PlayAnimation("die");
                    nextActionTime = (float)gameTime.TotalGameTime.TotalSeconds + DeadDelaySeconds;
                    player.GetExperience(ExperienceWorth);
                }
                else if (animations["die"].FinishedPlaying && gameTime.TotalGameTime.TotalSeconds > nextActionTime)
                    ChangeState(EnemyState.Dead);

                base.Update(gameTime);
                return;
            }
            #endregion

            #region GetHit
            if (invicible) { affectColor = affectColor == Color.Gray ? Color.White : Color.Gray; }
            if (time > invicibleEndTime) { affectColor = Color.White; invicible = false; invicibleEndTime = float.MaxValue; }//?

            if (gethit)
            {
                if (currentAnimation != "gethit")
                {
                    PlayAnimation("gethit", false, true, false);
                    Vector2 pushback = worldLocation - player.WorldLocation;
                    pushback.Normalize(); pushback.Y = 0; pushback.X *= 40;
                    velocity = pushback;
                    nextActionTime = (float)gameTime.TotalGameTime.TotalSeconds + GethitDelaySeconds;
                }

                if (time > nextActionTime)
                {
                    ChangeState(EnemyState.Chasing);
                    velocity = Vector2.Zero;
                    gethit = false;
                }

                if (RandomHelper.rand.NextDouble() < 0.007f)
                {
                    PlayAnimation("idle");
                    invicible = true;
                    invicibleEndTime = time + InvicibleTime;
                }

                base.Update(gameTime);
                return;
            }

            getHitCount = 0;
            #endregion

            #region Enemy State
            // Enemy movement
            switch (state)
            {
                // Enemies Move randomly around the map searching for the player
                // If they See() the player, they begin thinking what to do next
                // Init value: Wander start location, Wander destination
                case EnemyState.Wandering:
                    {
                        if (SeePlayer()) ChangeState(EnemyState.Confusing);  // Change state
                        else Wandering(gameTime);

                        break;
                    }

                // Enemies see something suspicous but not sure what to do
                // If they think ShouldChasePlayer(), they will change their
                // state to Chasing, Else, they will Wandering
                case EnemyState.Confusing:
                    {
                        PlayAnimation("idle", false, true, false);
                        if (ShouldChasePlayer()) ChangeState(EnemyState.Chasing);
                        else ChangeState(EnemyState.Wandering);
                        break;
                    }

                // Enemies chasing the player, if they lose range of the player
                // they will back to Wandering state, if they're close enough
                // they will start attacking
                case EnemyState.Chasing:
                    {
                        if (CanAttack())
                        {
                            if (time > nextAttackTime)
                                nextAttackTime = time + AttackDelaySeconds;

                            ChangeState(EnemyState.Attacking);
                        }
                        else if (LosedRange()) ChangeState(EnemyState.Wandering);
                        else ChasePlayer(gameTime);
                        break;
                    }

                // The most exciting state of course!, the enemy will Attack and 
                // use skills, they can Flee if health is too low or chase the
                // player if he is out of their Attack range
                case EnemyState.Attacking:
                    {
                        if (ShouldFlee() && !fleedBefore) { ChangeState(EnemyState.Fleeing); Flee(gameTime); }
                        else if (CanAttack() || attacking) Attack(gameTime);
                        else
                        {
                            ChangeState(EnemyState.Chasing);
                            nextActionTime = (float)gameTime.TotalGameTime.TotalSeconds + AttackDelaySeconds;
                        }

                        if (attacking && animations[currentAnimation].FinishedPlaying)
                        {
                            attacking = false;
                            projects.AddEnemyProject(project);
                        }

                        break;
                    }

                // Enemy is fleeing, if they are far is enough, they will Wandering
                // (or I say, recovering!). If they still in the player range, they
                // will have to decide to Flee or to 'dfg' again.
                case EnemyState.Fleeing:
                    {
                        fleedBefore = true;
                        if (Safe()) ChangeState(EnemyState.Wandering);
                        if ((float)gameTime.TotalGameTime.TotalSeconds < nextActionTime)
                        {
                            if (worldLocation.X <= 50 || (worldLocation.X >= Camera.WorldRectangle.Width - 200)) { flipped = !flipped; velocity.X *= -1; }
                            if (worldLocation.Y <= Const.ViewportTopMost || (worldLocation.Y >= Const.ViewportBottomMost)) velocity.Y *= -1;
                            break;     // Fleeing
                        }

                        ChangeState(EnemyState.Attacking);
                        break;
                    }
            }
            #endregion

            worldLocation = new Vector2(worldLocation.X, MathHelper.Clamp(worldLocation.Y, Const.ViewportTopMost, Const.ViewportBottomMost));
            base.Update(gameTime);
        }
        #endregion

        #region Constructor and Initalizer
        public Enemy(ModelType type, Vector2 pos)
        {
            // Get type and path
            this.type = type;
            int itype = (int)type;
            string path = Const.TexturePathCharacter + ModelInfomation.CharacterInformation[itype].Name + "\\";
            FrameInformation objInf = ModelInfomation.FrameInformation[itype];
            CharacterInformation baseInf = ModelInfomation.CharacterInformation[itype];

            // Base Information
            LoadCharacterValue(baseInf);

            // Load animation
            LoadAnimation(path, objInf);

            // Init object value
            worldLocation = pos;
            ChangeState(EnemyState.Wandering);
            wandersStartPosition = pos;
            gethit = false;
            PlayAnimation("idle");
        }

        private void LoadCharacterValue(CharacterInformation baseInf)
        {
            // Update value base on current diffucult to keep the
            // game balance. Current difficulty base on player's level
            // and the value you choose at the beginning of game
            float cap = GamesManager.Difficult;
            int level = player.Level;

            health = maxHealth = (int)(baseInf.MaxHealth * (1 + level / 20f) * cap);
            mana = maxMana = (int)(baseInf.MaxMana * (1 + level / 20f) * cap);
            attackPower = (int)(baseInf.AttackPower * (1 + level / 10f) * cap);
            armor = (int)(baseInf.Armor * (1 + level / 50f) * cap);
            speed = baseInf.MoveSpeed * cap;
            attackSpeed = baseInf.AttackSpeed * cap;
            healthRegen = baseInf.HealthRegen * (1 + level / 30f) / cap;
            manaRegen = baseInf.ManaRegen * (1 + level / 20f) / cap;
            attackRange = baseInf.AttackRange;
            sighRange = baseInf.SighRange;
            attackType = baseInf.AttackType;
            element = baseInf.Element;

            // Calculate exp worth
            expValue = (int)(attackPower / 10 + maxHealth / 80 + maxMana / 200);
        }

        private void LoadAnimation(string path, FrameInformation objInf)
        {
            // Common animations
            animations.Add("idle", new AnimationStrip(content.Load<Texture2D>(path + "Idle"), 1, objInf.IdleFrameCount, "idle"));
            animations.Add("walk", new AnimationStrip(content.Load<Texture2D>(path + "Walk"), 1, objInf.MoveFrameCount, "walk"));
            animations.Add("gethit", new AnimationStrip(content.Load<Texture2D>(path + "Gethit"), 1, objInf.GethitFrameCount, "gethit"));
            animations.Add("die", new AnimationStrip(content.Load<Texture2D>(path + "Die"), 1, objInf.DieFrameCount, "die"));
            animations.Add("Attack", new AnimationStrip(content.Load<Texture2D>(path + "Attack"), 1, objInf.AttackFrameCount, "Attack"));

            // Load skill 1 if avaiable, otherwise load Attack animation to skill 1
            if (objInf.PrimarySkillFrameCount != 0) animations.Add("skill1", new AnimationStrip(content.Load<Texture2D>(path + "Skill1"), 1, objInf.PrimarySkillFrameCount, "skill1"));
            else animations.Add("skill1", new AnimationStrip(content.Load<Texture2D>(path + "Attack"), 1, objInf.AttackFrameCount, "skill1"));

            // Load skill 2 if avaiable, otherwise check, if skill 1 is avaiable, load it to skill 2, otherwise load Attack instead
            if (objInf.SecondarySkillFrameCount != 0) animations.Add("skill2", new AnimationStrip(content.Load<Texture2D>(path + "Skill2"), 1, objInf.SecondarySkillFrameCount, "skill2"));
            else if (objInf.PrimarySkillFrameCount != 0) animations.Add("skill2", new AnimationStrip(content.Load<Texture2D>(path + "Skill1"), 1, objInf.PrimarySkillFrameCount, "skill2"));
            else animations.Add("skill2", new AnimationStrip(content.Load<Texture2D>(path + "Attack"), 1, objInf.AttackFrameCount, "skill2"));

            // Config animation
            animations["idle"].LoopAnimation = true; animations["idle"].FrameLength = 0.2f - speed / 2000;
            animations["walk"].LoopAnimation = true; animations["Move"].FrameLength = 0.18f - speed / 2000;
            animations["gethit"].NextAnimation = "idle"; animations["gethit"].FrameLength = 0.3f;
            animations["die"].FrameLength = 0.2f;
            animations["Attack"].NextAnimation = animations["skill1"].NextAnimation = animations["skill2"].NextAnimation = "idle";
        }
        #endregion

        #region Public Methods
        public void GetDamage(int dam)
        {
            if (!invicible)
            {
                //AudioManager.PlayCue("DamageNormal");

                // enemy get 90% - 110% dam
                dam = (int)((RandomHelper.rand.NextDouble() / 5 + 0.9f) * dam);

                health -= dam * (1 - armor / 100);
                getHitCount++;
                gethit = true;
            }
        }
        #endregion
    }
}
